﻿using System;
using System.Text;

namespace SharpTag.ID3
{
    /// <summary>
    /// Concrete implementation of the <see cref="IID3v1Tag"/> interface.
    /// </summary>
    public class ID3V1Tag : IID3v1Tag
    {
        private const int TAGDescriptorOffset = 0;
        private const int TitleOffset = 3;
        private const int ArtistOffset = 33;
        private const int AlbumOffset = 63;
        private const int YearOffset = 93;
        private const int CommentOffset = 97;
        private const int StringLength = 30;
        private const int YearLength = 4;
        private const int TagLength = 128;
        private const int IdLength = 3;
        private const double ID3v1Version = 1.0;
        private const double ID3v11Version = 1.1;
        private const char ZeroedByte = '\0';

        /// <summary>
        /// Class constructor.
        /// </summary>
        public ID3V1Tag()
        {
            IsDirty = false;
        }

        /// <summary>
        /// Class constructor
        /// </summary>
        /// <param name="offset">Offset position of the tag within the raw file.</param>
        public ID3V1Tag(long offset)
        {
            Offset = offset;
            Name = "ID3v1";
            Description = "Standard ID3v1 Tag";
            IsDirty = false;
        }

        /// <summary>
        /// Gets whether or not the Tag has been modified.
        /// </summary>
        public bool IsDirty { get; private set; }

        /// <summary>
        /// Gets the Name of the Tag.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the Description of the Tag.
        /// </summary>
        public string Description { get; private set; }

        /// <summary>
        /// Gets the Offset of the Tag.
        /// </summary>
        public long Offset { get; private set; }

        /// <summary>
        /// Gets the Version of the Tag.
        /// </summary>
        public double Version { get; private set; }

        /// <summary>
        /// Gets the Album found in the Tag.
        /// </summary>
        public string Album { get; private set; }

        /// <summary>
        /// Gets the AlbumTrack found in the Tag.
        /// </summary>
        public int AlbumTrack { get; private set; }

        /// <summary>
        /// Gets the Artist found in the Tag.
        /// </summary>
        public string Artist { get; private set; }

        /// <summary>
        /// Gets the Comment found in the Tag.
        /// </summary>
        public string Comment { get; private set; }

        /// <summary>
        /// Gets the Title found in the Tag.
        /// </summary>
        public string Title { get; private set; }

        /// <summary>
        /// Gets the Year found in the Tag.
        /// </summary>
        public int Year { get; private set; }

        /// <summary>
        /// Gets the Genre found in the Tag.
        /// </summary>
        public string Genre { get; private set; }

        /// <summary>
        /// Sets the Album of the Tag.
        /// </summary>
        /// <param name="albumName">The Album name.</param>
        /// <remarks>Album can be set to any valid string, but when written back to the file it will be made to a max of 30 characters.</remarks>
        public void SetAlbum(string albumName)
        {
            if (!string.IsNullOrEmpty(albumName))
            {
                Album = albumName;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Album Track of the Tag.
        /// </summary>
        /// <param name="albumTrack">The Track number.</param>
        public void SetAlbumTrack(int albumTrack)
        {
            if (albumTrack > 0 && albumTrack < int.MaxValue)
            {
                AlbumTrack = albumTrack;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Artist name of the Tag.
        /// </summary>
        /// <param name="artistName">The Artist name.</param>
        public void SetArtist(string artistName)
        {
            if (!string.IsNullOrEmpty(artistName))
            {
                Artist = artistName;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Comment of the Tag.
        /// </summary>
        /// <param name="comment">The Comment string.</param>
        public void SetComment(string comment)
        {
            if (!string.IsNullOrEmpty(comment))
            {
                Comment = comment;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Title of the Tag.
        /// </summary>
        /// <param name="title">The Title string.</param>
        public void SetTitle(string title)
        {
            if (!string.IsNullOrEmpty(title))
            {
                Title = title;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Year of the Tag.
        /// </summary>
        /// <param name="year">The Year integer.</param>
        public void SetYear(int year)
        {
            if (year > 0 && year < int.MaxValue)
            {
                Year = year;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Sets the Genre of the Tag.
        /// </summary>
        /// <param name="genre">The Genre string.</param>
        public void SetGenre(string genre)
        {
            if (!string.IsNullOrEmpty(genre))
            {
                Genre = genre;
                IsDirty = true;
            }
        }

        /// <summary>
        /// Parses the raw tag data into the Tag properties.
        /// </summary>
        /// <param name="tagBytes">A byte array containing the raw tag data.</param>
        public void ParseTagData(byte[] tagBytes)
        {
            if (tagBytes == null || tagBytes.Length == 0) return;

            Title = tagBytes.GetOffsetValue(TitleOffset, StringLength).Trim(ZeroedByte);
            Artist = tagBytes.GetOffsetValue(ArtistOffset, StringLength).Trim(ZeroedByte);
            Album = tagBytes.GetOffsetValue(AlbumOffset, StringLength).Trim(ZeroedByte);
            Year = Convert.ToInt32(tagBytes.GetOffsetValue(YearOffset, YearLength).Trim(ZeroedByte));
            Comment = tagBytes.GetOffsetValue(CommentOffset, StringLength).Trim(ZeroedByte);

            if (Comment[StringLength - 2] == '\0' && Comment[StringLength - 1] != ' ')
            {
                AlbumTrack = Comment[StringLength - 1];
                Comment = Comment.Substring(0, StringLength - 2);
                Comment = Comment.Trim(ZeroedByte);
                if (Comment.Trim().Length == 0)
                    Comment = "";
                Version = ID3v11Version;
            }
            else
            {
                if (Comment.Trim().Length != 0)
                    Comment = "";
                AlbumTrack = 0;
                Version = ID3v1Version;
            }

            char genreByte = (char)tagBytes[TagLength - 1];
            if (genreByte != ZeroedByte)
                Genre = ((ID3.Genre)genreByte).ToString();
            else
                Genre = ID3.Genre.Unknown.ToString();

            IsDirty = true;
        }

        /// <summary>
        /// Converts the ID3v1 data into a byte array to be written out to the physical file.
        /// </summary>
        /// <returns>A byte array that contains the raw Tag data.</returns>
        public byte[] PrepareBytes()
        {
            var returnBytes = new byte[128];

            var iden = "TAG";
            var title = Title.SetToLength(30, '\0');
            var artist = Artist.SetToLength(30, '\0');
            var album = Album.SetToLength(30, '\0');
            var track = Convert.ToInt32(AlbumTrack);
            var year = Year.ToString();
            var comment = Comment.SetToLength(30, ' ').Insert(29, "\0");
            ID3.Genre GenreID;
            var id = Enum.TryParse<Genre>(Genre, out GenreID) == true ? (int)GenreID : 0;
            byte[] commentBytes = new byte[30];
            Array.Copy(Encoding.ASCII.GetBytes(comment), commentBytes, 30);

            if (Version == ID3v11Version)
            {
                commentBytes[28] = 0;
                commentBytes[29] = byte.Parse(track.ToString());
            }

            Array.Copy(Encoding.ASCII.GetBytes(iden), returnBytes, 3);
            Array.Copy(Encoding.ASCII.GetBytes(title), 0, returnBytes, 3, 30);
            Array.Copy(Encoding.ASCII.GetBytes(artist), 0, returnBytes, 33, 30);
            Array.Copy(Encoding.ASCII.GetBytes(album), 0, returnBytes, 63, 30);
            Array.Copy(Encoding.ASCII.GetBytes(year), 0, returnBytes, 93, 4);
            Array.Copy(commentBytes, 0, returnBytes, 97, 30);
            returnBytes[127] = (byte)id;

            return returnBytes;
        }

    }
}
